/*
 * @file iso9660_shell.h
 *
 *  Shell commands used to debug ISO9660 volumes.
 *
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>

#include "iso9660.h"
#include "iso9660fs.h"
#include "iso9660_io.h"
#include "iso9660_dbg.h"
#include "iso9660_susp.h"
#include "iso9660_record.h"

#include "iso9660_shell.h"


/*
 *  Export the ISO9660 debug shell handler
 *
 *  TODO: should be located elsewhere in the trunk
 */
rtems_shell_cmd_t Shell_ISO9660_Command = {
  "debugiso9660",                               /* name */
  "debugiso9660 {-h | -l | <path> <command> [args] }",  /* usage */
  "user",                                       /* topic */
  rtems_shell_debugiso9660,                     /* command */
  NULL,                                         /* alias */
  NULL                                          /* next */
};

rtems_shell_alias_t Shell_ISO9660_Alias = {
  "debugiso9660",                  /* command */
  "debugiso"                       /* alias */
};

/*
 * Type of iso9660 sub-routines
 */

typedef int (*iso9660_dbg_shell_function_t) (iso9660_fs_info_t *fs, char *path, int argc, char **argv);


/*
 * Type of a sub-routine entry
 */

typedef struct
{
  iso9660_dbg_shell_function_t  handler;
  char                         *name;
  char                         *usage;
} iso9660_dbg_shell_entry_t;


void iso9660_debug_version(
    iso9660_version_t   version
)
{
  printf("%s with ", iso9660_version_supports(version, ISO9660_STANDARD_1999)?"ISO9660:1999":"ISO9660/ECMA-119");

  if (iso9660_version_supports(version, ISO9660_EXTENSION_JOLIET_LEVEL1))
    printf("joliet level 1, ");
  if (iso9660_version_supports(version, ISO9660_EXTENSION_JOLIET_LEVEL2))
    printf("joliet level 2, ");
  if (iso9660_version_supports(version, ISO9660_EXTENSION_JOLIET_LEVEL3))
    printf("joliet level 3, ");
  if (iso9660_version_supports(version, ISO9660_EXTENSION_SUSP))
    printf("system use sharing protocol, ");
  if (iso9660_version_supports(version, ISO9660_EXTENSION_ROCK_RIDGE))
    printf("rock ridge, ");
  if (!iso9660_version_supports(version, ISO9660_EXTENSION_ALL))
    printf("no extension");
}

/**
 *   Displays information on the given volume
 *
 * @param fs   Pointer to the iso9660 filesystem information structure
 * @param argc Number of arguments related to this routine
 * @param argv Arguments related to this routine
 *
 * @return 0
 */
int iso9660_dbg_volume(
    iso9660_fs_info_t   *fs,
    char                *path,
    int                  argc,
    char               **argv
)
{
  iso9660_volume_info_t *vol = &fs->volume;

  printf("       Volume identifier : `%s'\n",        vol->volume_identifier);
  printf("       System identifier : `%s'\n",        vol->system_identifier);
  printf("         Sequence number : %" PRIu16 "\n", vol->sequence_num);
  printf("              Block size : %" PRIu16 "\n", vol->logical_block_size);
  printf("              Space size : %" PRIu16 "\n", vol->space_size);
  printf("                Set size : %" PRIu16 "\n", vol->set_size);
  printf("         Path table size : %" PRIu16 "\n", vol->path_table_size);
  printf("Path table location (BE) : %" PRIu16 "\n", vol->m_path_table);
  printf("    Path table copy (BE) : %" PRIu16 "\n", vol->m_path_table_2);
  printf("Path table location (LE) : %" PRIu16 "\n", vol->l_path_table);
  printf("    Path table copy (LE) : %" PRIu16 "\n", vol->l_path_table_2);
  printf("          Volume version : ");
    iso9660_debug_version(fs->version);
    printf("\n");
  printf("\n              Mount mask : ");
    iso9660_debug_version(fs->version_mask);
    printf("\n");

  return 0;
}


int iso9660_dbg_susp(
    iso9660_fs_info_t   *fs,
    char                *path,
    int                  argc,
    char               **argv
)
{
  iso9660_record_t *record;
  iso9660_susp_list_t susp_list;
  iso9660_susp_entry_t *susp_entry;
  rtems_filesystem_location_info_t pathloc;
  int rc;

  rc = rtems_filesystem_evaluate_path(path, strlen(path), 0, &pathloc, TRUE);
  if (rc)
    return -1;

  record = rtems_iso9660_get_record_pathloc(&pathloc);

  if (record->system_use_size <= 0)
  {
    printf("No system use area for this record.\n");
    rtems_filesystem_freenode(&pathloc);
    return 0;
  }

  rc = iso9660_susp_entry_open_record(record, &susp_list);

  if (rc == -1)
    rtems_iso9660_error_and_return_minus_one(EIO,
        "susp_update_fs_info: Failed to init SUSP list");

  printf("List of SUSP entries (system use area is %d bytes) :\n",
      record->system_use_size);

  rc = 0;
  while (rc != RTEMS_ISO9660_SUSP_LIST_END && rc != -1)
  {
    rc = iso9660_susp_list_next_entry(&susp_list, &susp_entry);
    printf("  > %.2s (%4X)  len=%d, version=%d\n",
        ISO9660_SUSP_SIGNATURE_STR(susp_entry),
        ISO9660_SUSP_SIGNATURE(susp_entry),
        ISO9660_SUSP_SIZE(susp_entry),
        ISO9660_SUSP_VERSION(susp_entry));

    switch (ISO9660_SUSP_SIGNATURE(susp_entry))
    {
      case ISO9660_SUSP_SIGNATURE_NM:
        printf(" .. Alternate name: %.*s\n",
            ISO9660_SUSP_NM_NAME_LEN(susp_entry),
            ISO9660_SUSP_NM_NAME(susp_entry));

        printf(" .. Flags: ");
        if (ISO9660_SUSP_NM_FLAGS(susp_entry) & ISO9660_SUSP_NM_FLAG_CONTINUE)
          printf("continue, ");
        if (ISO9660_SUSP_NM_FLAGS(susp_entry) & ISO9660_SUSP_NM_FLAG_CURRENT)
          printf("current, ");
        if (ISO9660_SUSP_NM_FLAGS(susp_entry) & ISO9660_SUSP_NM_FLAG_PARENT)
          printf("parent, ");
        printf("\n");
        break;


      case ISO9660_SUSP_SIGNATURE_PX:
        printf(" .. Mode: %lo\n", ISO9660_SUSP_PX_INO(susp_entry));
        printf(" .. uid: %ld\n", ISO9660_SUSP_PX_UID(susp_entry));
        printf(" .. gid: %ld\n", ISO9660_SUSP_PX_GID(susp_entry));
        printf(" .. nlinks: %ld\n", ISO9660_SUSP_PX_NLINKS(susp_entry));
      break;
    }
  }

  iso9660_susp_list_free(&susp_list);
  rtems_filesystem_freenode(&pathloc);
  return 0;
}


/**
 *   Retrieve the iso9660 filesystem information structure pointer
 *  from a given path. The function fails if the path does not belong
 *  to a mounted iso9660 volume.
 *
 *  @param path Path
 *  @param fs A pointer to a filesystem information structure pointer to be
 *    filled if the path is OK.
 *
 *  @return 0 if the pointer is filled, -1 if an error occured.
 */
int iso9660_dbg_shell_getfs(
    char               *path,
    iso9660_fs_info_t **fs
)
{
  rtems_filesystem_location_info_t pathloc;
  struct statvfs sv;
  int rc;

  rc = statvfs(path, &sv);
  if (rc)
    return -1;

  if (sv.f_fsid != ISO9660_MAGIC_ID)
    return -1;

  rc = rtems_filesystem_evaluate_path(path, strlen(path), 0, &pathloc, TRUE);
  if (rc)
    return -1;

  *fs = rtems_iso9660_get_fsinfo_pathloc(&pathloc);
  rtems_filesystem_freenode(&pathloc);

  return 0;
}


/**
 *   Print the help on the debug helper usage.
 *
 * @param prg Called function name.
 */
void rtems_shell_debugiso9660_usage(
    char *prg
)
{
  printf("Usage: %s {-h | -l | <path> <command> [args] }\n", prg);
}


/**
 * Shell helper to debug ISO9660 volumes
 *
 * @param argc The argument count.
 * @param argv The argument variables.
 * @return int The exit code for the command. A 0 is no error.
 */
int rtems_shell_debugiso9660(int argc, char *argv[])
{
  int argi, i, rc;
  iso9660_fs_info_t *fs;

  iso9660_dbg_shell_entry_t subroutines[] =
    {
        { iso9660_dbg_volume, "volume", "volume" },
        { iso9660_dbg_susp, "susp", "susp" },
        { NULL, NULL, NULL }
    };

  /* Bad number of arguments */
  if (argc <= 2)
  {
    rtems_shell_debugiso9660_usage(argv[0]);
    return -1;
  }

  /* Skipping opt characters */
  for (argi = 1; argi < argc; argi++)
  {
    if (argv[argi][0] != '-')
      break;
  }

  /* Try to get the iso9660 filesystem information structure from the
   * given path
   */
  rc = iso9660_dbg_shell_getfs(argv[argi], &fs);
  if (rc) {
    printf("Cannot retrieve ISO9660 volume information from given path.\n");
    return -1;
  }

  /* Find and call the subroutine handler */
  for (i = 0; subroutines[i].handler != NULL; i++)
  {
    if (!strcmp(argv[argi+1], subroutines[i].name))
      return subroutines[i].handler(fs, argv[argi], argc-argi-2, argv+argi+2);
  }

  printf("No such subroutine `%s', use -l option to list subroutines.\n", argv[i+1]);
  return 0;
}
